class Watcher {
  construct(vm, key, callback) {
    this.vm = vm;
    this.key = key;
    this.callback = callback;

    Dep.target = this;
    this.oldValue = vm[key]; // 读取数据
    Dep.target = null;
  }

  update() {
    const newValue = this.vm[this.key];
    if (newValue === this.oldValue) return;
    this.callback(newValue);
    this.oldValue = newValue;
  }
}

/**
 *  compile进行模板功能处理
 * 分析dom结构以及结构中的文本，可能存在的属性
 * 判断属性是否存在插值表达式，或特殊名称的指令，然后找到响应式数据的名称，将名称提取出来，进行标记，
 * 也就是为这个位置创建一个观察者Watcher，Watcher关联到某一个Observer里面，
 * 就可以在Obeserver更新时，进行内容更新了
 */
class Compile {
  constructor(vm) {
    this.vm = vm;
    this.el = vm.$el;
  }
  // 进行文本处理
  compile(el) {
    console.log(el);
    const childNodes = el.childNodes;
    Array.from(childNodes).forEach((node) => {
      if (node.nodeTypy === 3) {
        this.compileText(node);
      } else if (node.nodeTypy === 1) {
        console.log("node.nodeTypy === 1");
      }
      if (node.childNodes && node.childNodes.length) {
        this.compile(node);
      }
    });
  }
  // 处理文本，正则
  compileText(node) {
    const reg = /\{\{(.+?)\}\}/g;
    // 内容替换
    const value = node.textContent.replace(/\s/g, "");
    const tokens = [];
    let result,
      index,
      lastIndex = 0;
    while ((result = reg.exec(value))) {
      index = result.index;
      if (index > lastIndex) {
        tokens.push(value.slice(lastIndex, index));
      }
      const key = result[1].trim;
      tokens.push(this.vm[key]);

      lastIndex = index + result[0].length;
      const pos = tokens.length - 1;
      // 更新操作
      new Watcher(this.vm, key, (newValue) => {
        tokens[pos] = newValue;
        node.textContext = tokens.join("");
      });
    }
    if (lastIndex < value.length) {
      tokens.push(value.slice(lastIndex));
    }

    if (tokens.length) {
      node.textContent = tokens.join("");
    }
  }
}

class Dep {
  // Dep不需要传值，
  constructor() {
    // 存储订阅的列表， 保存所有订阅者，即视图中的区域
    this.subs = [];
  }
  addSub(sub) {
    // 向subs里面添加值
    this.subs.push(sub);
  }
  notify() {
    // 通知订阅者，调用update方法，让订阅者更新
    this.subs.forEach((sub) => sub.update());
  }
}

class Observer {
  constructor(data) {
    this.data = data;
    this.dep = new Dep();
    /**
     * walk来进行遍历方法的封装
     * 此处用来进行初始流程的设置，不在这里进行具体的功能逻辑设置
     */
    this.walk(this.data);
  }
  walk(data) {
    // dep 做变量在dom中的位置的记录
    const dep = this.dep;
    // 用一个独立函数defineProperty对每一个值进行处理，源码叫defineReactive
    Object.keys(data).forEach((key) =>
      defineProperty(data, key, data[key], dep)
    );
  }
}

function defineProperty(data, key, value, dep) {
  // 首先判断当前value是不是对象, 如果是对象，就再用Observer监视
  if (typeof value === "object" && value !== null) {
    return new Observer(value);
  }

  // 如果是普通值，就需要用object.defineproperty来处理
  Object.defineProperty(data, key, {
    enumerable: true,
    configurable: true,
    get() {
      // 在读取的时候添加观察者
      Dep.target && dep.addSub(Dep.target);
      return value;
    },
    set(newVal) {
      if (value === newVal) return; // 新值和旧值一样时，直接返回
      value = newVal;
      if (typeof value === "object" && value !== null) {
        new Observer(value);
      }
      // 在修改时通知更新
      dep.notify();
    },
  });
}
/**
 * 视图内容跟响应数据产生关联
 * 模板解析的时候，会依次分析模板里的每个结构，找到里面的变量，做初始的设置，即展示初始数据
 * 更新时，会再找到这些位置，进行内容更新
 */

class Vue {
  constructor(options) {
    this.$options = options || {};
    this.$data = options.data || {};
    const el = options.el;
    this.$el = typeof el === "string" ? document.querySelector("el") : el;

    // 1、将属性注入Vue实例，可以通过vm直接访问，让我们操作更便捷
    proxy(this, this.$data);

    // 2、创建Observer，进行data属性变化的监视
    new Observer(this.$data);
    // 3、视图解析
    new Compile(this);
  }
}

function proxy(target, data) {
  /**
   * 做一个遍历，让target在进行属性设置的时候，让getter和setter都指向data里面对应的属性
   * 将data里面的属性放到vm上，并添加上getter和setter
   */
  Object.keys(data).forEach((key) => {
    Object.defineProperty(target, key, {
      enumerable: true,
      configurable: true,
      get() {
        return data[key];
        // 在第一次读取时，对该位置进行记录，方便修改时更新此位置
      },
      set(newVal) {
        data[key] = newVal;
      },
    });
  });
}
